#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

#define CAPACITY 4

typedef struct {
	int buf[CAPACITY];
	int in;
	int out;
}buff;

buff buffer1, buffer2;

int get_item(buff *buffer)
{
	int item;

	item = buffer->buf[buffer->out];
	buffer->out = (buffer->out + 1) % CAPACITY;
	return item;
}

void put_item(int item, buff *buffer)
{
	buffer->buf[buffer->in] = item;
	buffer->in = (buffer->in + 1) % CAPACITY;
}

typedef struct {
	int value;
	pthread_mutex_t mutex;
	pthread_cond_t cond;
}sema_t;

void sema_init(sema_t *sema, int value)
{
	sema->value = value;
	pthread_mutex_init(&sema->mutex, NULL);
	pthread_cond_init(&sema->cond, NULL);
}

void sema_destroy(sema_t *sema)
{
	sema->value = 0;
	pthread_mutex_destroy(&sema->mutex);
	pthread_cond_destroy(&sema->cond);
}

void sema_wait(sema_t *sema)
{
	pthread_mutex_lock(&sema->mutex);
	while (sema->value <= 0)
		pthread_cond_wait(&sema->cond, &sema->mutex);
	sema->value--;
	pthread_mutex_unlock(&sema->mutex);
}

void sema_signal(sema_t *sema)
{
	pthread_mutex_lock(&sema->mutex);
	++sema->value;
	pthread_cond_signal(&sema->cond);
	pthread_mutex_unlock(&sema->mutex);
}

sema_t mutex_sema1, mutex_sema2;
sema_t empty_buffer_sema1, empty_buffer_sema2;
sema_t full_buffer_sema1, full_buffer_sema2;

#define ITEM_COUNT (CAPACITY * 2)

void *produce(void *arg)
{
	int i;
	int item;

	for (i = 0; i < ITEM_COUNT; ++i) {
		sema_wait(&empty_buffer_sema1);
		sema_wait(&mutex_sema1);

		item = i + 'a';
		put_item(item, &buffer1);
		//printf("produce item: %c\n", item);

		sema_signal(&mutex_sema1);
		sema_signal(&full_buffer_sema1);
	}
	return NULL;
}

void *compute(void *arg)
{
	int i;
	int item;

	for (i = 0; i < ITEM_COUNT; ++i) {
		sema_wait(&full_buffer_sema1);
		sema_wait(&mutex_sema1);

		item = get_item(&buffer1);
		//printf("	compute got item: %c\n", item);

		sema_signal(&mutex_sema1);
		sema_signal(&empty_buffer_sema1);

		sema_wait(&empty_buffer_sema2);
		sema_wait(&mutex_sema2);

		item -= 32;
		put_item(item, &buffer2);
		//printf("	compute result item: %c\n", item);

		sema_signal(&mutex_sema2);
		sema_signal(&full_buffer_sema2);
	}
	return NULL;
}

void *consume(void *arg)
{
	int i;
	int item;

	for (i = 0; i < ITEM_COUNT; ++i) {
		sema_wait(&full_buffer_sema2);
		sema_wait(&mutex_sema2);

		item = get_item(&buffer2);
		//printf("		consume item: %c\n", item);
		printf("%c\n", item);
		sema_signal(&mutex_sema2);
		sema_signal(&empty_buffer_sema2);
	}
	return NULL;
}

int main()
{
	pthread_t computer_tid, consumer_tid;

	sema_init(&mutex_sema1, 1);
	sema_init(&mutex_sema2, 1);
	sema_init(&empty_buffer_sema1, CAPACITY - 1);
	sema_init(&empty_buffer_sema2, CAPACITY - 1);
	sema_init(&full_buffer_sema1, 0);
	sema_init(&full_buffer_sema2, 0);

	//pthread_create(&consumer_tid, NULL, consume, NULL);
	pthread_create(&computer_tid, NULL, compute, NULL);
	pthread_create(&consumer_tid, NULL, consume, NULL);
	produce(NULL);

	pthread_join(computer_tid, NULL);
	pthread_join(consumer_tid, NULL);

	sema_destroy(&mutex_sema1);
	sema_destroy(&mutex_sema2);
	sema_destroy(&empty_buffer_sema1);
	sema_destroy(&empty_buffer_sema2);
	sema_destroy(&full_buffer_sema1);
	sema_destroy(&full_buffer_sema2);
	return 0;
}

